Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.geekflare.com/llms.txt

Use this file to discover all available pages before exploring further.

The Geekflare SDK must only run server-side — your API key must never be exposed to the browser. Next.js makes this straightforward with Route Handlers, Server Actions, and Server Components.

Prerequisites

  • Next.js 14+ with the App Router
  • Node.js 18+
  • A Geekflare API key — get one free

Installation

bash pnpm add @geekflare/api-node

Set your API key

Add your API key to .env.local:
.env.local
GEEKFLARE_API_KEY=your_api_key_here
.env.local is gitignored by default in Next.js. Never prefix this variable with NEXT_PUBLIC_ — that would expose it to the browser.

Shared client

Create a single client instance to reuse across your app:
lib/geekflare.ts
import { GeekflareClient } from "@geekflare/api-node";

export const geekflare = new GeekflareClient({
  apiKey: process.env.GEEKFLARE_API_KEY!,
});

Web Scraping

Route Handler

Accepts a POST request from your client-side code and returns scraped content.
app/api/scrape/route.ts
import { NextResponse } from "next/server";
import { geekflare } from "@/lib/geekflare";

export async function POST(request: Request) {
  const { url } = await request.json();

  if (!url) {
    return NextResponse.json({ error: "url is required" }, { status: 400 });
  }

  try {
    const result = await geekflare.webScrape({
      url,
      renderJS: true,
      blockAds: true,
      format: "html,markdown",
    });

    return NextResponse.json(result);
  } catch (error: any) {
    return NextResponse.json(
      { error: error.message ?? "Scrape failed" },
      { status: error?.status ?? 500 },
    );
  }
}

Server Action

Use directly from Client Components without a round-trip to a route:
app/actions.ts
"use server";

import { geekflare } from "@/lib/geekflare";

export async function scrapeUrl(url: string) {
  const result = await geekflare.webScrape({
    url,
    renderJS: true,
    blockAds: true,
    format: "markdown",
  });

  return result;
}

Server Component

Fetch and render scraped content directly in a Server Component:
app/scrape/page.tsx
import { geekflare } from "@/lib/geekflare";

export default async function ScrapePage() {
  const result = await geekflare.webScrape({
    url: "https://toscrape.com/",
    format: "markdown",
  });

  // result.data is a URL pointing to the scraped output file
  return (
    <article className="prose mx-auto py-12">
      <h1>Scraped Content</h1>
      <p>
        Output file: <a href={result.data as string}>{result.data as string}</a>
      </p>
    </article>
  );
}

Route Handler

app/api/search/route.ts
import { NextResponse } from "next/server";
import { geekflare } from "@/lib/geekflare";

export async function POST(request: Request) {
  const { query } = await request.json();

  if (!query) {
    return NextResponse.json({ error: "query is required" }, { status: 400 });
  }

  try {
    const result = await geekflare.search({
      query,
      limit: 10,
      location: "us",
      source: "web",
      format: "json",
    });

    return NextResponse.json(result);
  } catch (error: any) {
    return NextResponse.json(
      { error: error.message ?? "Search failed" },
      { status: error?.status ?? 500 },
    );
  }
}

Server Action

app/actions.ts
"use server";

import { geekflare } from "@/lib/geekflare";

export async function searchWeb(query: string) {
  const result = await geekflare.search({
    query,
    limit: 10,
    location: "us",
    source: "web",
    format: "json",
  });

  return result.data ?? [];
}

Server Component

app/search/page.tsx
import { geekflare } from "@/lib/geekflare";

export default async function SearchPage({
  searchParams,
}: {
  searchParams: { q?: string };
}) {
  const query = searchParams.q ?? "Next.js best practices";

  const result = await geekflare.search({
    query,
    limit: 10,
    location: "us",
    format: "json",
  });

  return (
    <div className="max-w-2xl mx-auto py-12">
      <h1 className="text-2xl font-bold mb-6">Results for: {query}</h1>
      <ul className="space-y-4">
        {result.data?.map((item: any) => (
          <li key={item.url}>
            <a
              href={item.url}
              className="text-blue-600 hover:underline font-medium"
            >
              {item.title}
            </a>
            <p className="text-sm text-gray-600 mt-1">{item.snippet}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

Screenshot

Route Handler

app/api/screenshot/route.ts
import { NextResponse } from "next/server";
import { geekflare } from "@/lib/geekflare";

export async function POST(request: Request) {
  const { url } = await request.json();

  if (!url) {
    return NextResponse.json({ error: "url is required" }, { status: 400 });
  }

  try {
    const result = await geekflare.screenshot({
      url,
      type: "png",
      fullPage: true,
      device: "desktop",
      viewportWidth: 1280,
      viewportHeight: 800,
      blockAds: true,
      hideCookie: true,
    });

    return NextResponse.json(result);
  } catch (error: any) {
    return NextResponse.json(
      { error: error.message ?? "Screenshot failed" },
      { status: error?.status ?? 500 },
    );
  }
}

Server Component

Render the screenshot directly in a page using next/image:
app/preview/page.tsx
import Image from "next/image";
import { geekflare } from "@/lib/geekflare";

export default async function PreviewPage() {
  const result = await geekflare.screenshot({
    url: "https://example.com",
    type: "png",
    fullPage: false,
    viewportWidth: 1280,
    viewportHeight: 800,
    blockAds: true,
    hideCookie: true,
  });

  // result.data is the screenshot URL directly
  const screenshotUrl = result.data as string;

  return (
    <div className="p-8">
      <h1 className="text-xl font-semibold mb-4">Site Preview</h1>
      {screenshotUrl && (
        <Image
          src={screenshotUrl}
          alt="Site screenshot"
          width={1280}
          height={800}
          className="rounded-lg border shadow"
        />
      )}
    </div>
  );
}

Error handling

Handle errors consistently across Route Handlers and Server Actions.

Route Handler pattern

try {
  const result = await geekflare.webScrape({ url });
  return NextResponse.json(result);
} catch (error: any) {
  const code = error?.apiCode ?? error?.status ?? 500;
  const message = error?.message ?? "An unexpected error occurred";

  if (code === 429) {
    return NextResponse.json(
      { error: "Rate limit exceeded. Please try again shortly." },
      { status: 429 },
    );
  }

  if (code === 402) {
    return NextResponse.json(
      { error: "API credits exhausted." },
      { status: 402 },
    );
  }

  return NextResponse.json({ error: message }, { status: code });
}

Server Action pattern

"use server";

export async function safeScrape(url: string) {
  try {
    const result = await geekflare.webScrape({ url, format: "markdown" });
    return { data: result.data, error: null };
  } catch (error: any) {
    return { data: null, error: error.message ?? "Failed to scrape" };
  }
}
Consume it safely in a Client Component:
const { data, error } = await safeScrape(url);

if (error) {
  return <p className="text-red-500">{error}</p>;
}

Next steps