//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Use a API do Pixabay para pesquisar e exibir fotos e vídeos gratuitos. As imagens do Pixabay são lançadas sob sua própria licença, que permite o uso comercial gratuito sem atribuição (embora a atribuição seja apreciada).
Passo 1: Obtenha uma chave de API
.env.local:PIXABAY_API_KEY=your_api_key_herePasso 2: Crie um cliente de 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();
}Um navegador de fotos com filtro de categoria:
// 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>
);
}key (não um cabeçalho)./api/) aceita parâmetros incluindo q (termo de busca), image_type, orientation, category, min_width, min_height, colors, per_page (3-200) e page.totalHits (limitado a 500 para usuários não listados na lista branca), total (total real) e hits (array de objetos de imagem).previewURL (150px), webformatURL (640px), largeImageURL (1280px) e fullHDURL / imageURL (disponíveis com acesso completo à API)./api/videos/ com parâmetros semelhantes e retorna objetos de vídeo com URLs de múltiplas resoluções.Busca de vídeos:
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();
}image_type, orientation e category.totalHits é limitado a 500 para usuários padrão da API. Para acessar mais resultados, solicite acesso completo à API.webformatURL as imagens podem ter uma marca d'água do Pixabay em alguns casos para requisições de usuários não logados. Certifique-se de que sua chave de API é válida.per_page aceita valores entre 3 e 200. Valores fora desse intervalo retornam um erro.cdn.pixabay.com e pixabay.com a remotePatterns em next.config.ts para que next/image funcione.| Abordagem | Prós | Contras |
|---|---|---|
| API Pixabay | Uso comercial gratuito, sem necessidade de atribuição, também vídeos | Média de qualidade inferior, limite de 500 resultados |
| API Unsplash | Fotos de maior qualidade, biblioteca maior | Atribuição necessária |
| API Pexels | Boa qualidade, limites generosos, sem necessidade de atribuição | Biblioteca menor |
| API Flickr | Biblioteca massiva, opções de Creative Commons | Licenciamento complexo por foto |
key, não como um cabeçalho.previewURL -- 150px de largura.webformatURL -- 640px de largura.largeImageURL -- 1280px de largura.fullHDURL e imageURL estão disponíveis apenas com acesso completo à API.totalHits é limitado a 500 para usuários padrão da API (não listados na lista branca).total mostra o total real, mas você não pode paginar além de 500 resultados.const res = await fetch(`https://pixabay.com/api/videos/?${params}`, {
next: { revalidate: 3600 },
});Use o endpoint /api/videos/ com parâmetros semelhantes à busca de imagens.
ImageType, Orientation e Category.images: {
remotePatterns: [
{ protocol: "https", hostname: "cdn.pixabay.com" },
{ protocol: "https", hostname: "pixabay.com" },
],
}useRouter().push() para navegar com parâmetros de consulta atualizados.selectCategory constrói um objeto URLSearchParams com a consulta e a categoria opcional.searchParams e busca resultados filtrados.Revisado por Chris St. John·Última atualização: 7 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥