Screenshot API with Next.js App Router: Complete Guide

Next.js App Router introduced a fundamentally new model for React server components, route handlers, and server actions. Integrating SnapAPI into an App Router project differs from the Pages Router approach in important ways. This guide covers every integration pattern for Next.js 13+ with the App Router.

Route Handler for Server-Side Capture

Create a Route Handler at app/api/screenshot/route.ts to keep your API key server-side and never exposed to the browser:

import { NextRequest, NextResponse } from "next/server"

export async function GET(request: NextRequest) {
  const url = request.nextUrl.searchParams.get("url")
  if (!url) return NextResponse.json({ error: "url required" }, { status: 400 })

  const params = new URLSearchParams({
    access_key: process.env.SNAPAPI_KEY!,
    url,
    response_type: "json",
    width: "1280",
    full_page: "true"
  })

  const res = await fetch(`https://snapapi.pics/api/screenshot?${params}`, {
    next: { revalidate: 3600 }
  })
  const data = await res.json()
  return NextResponse.json(data)
}

Server Component Integration

In App Router, Server Components can call the SnapAPI endpoint directly during server-side rendering. This is ideal for generating OG images or page previews at render time with built-in caching:

async function getScreenshot(url: string) {
  const params = new URLSearchParams({
    access_key: process.env.SNAPAPI_KEY!,
    url,
    response_type: "json"
  })
  const res = await fetch(`https://snapapi.pics/api/screenshot?${params}`, {
    next: { revalidate: 86400 }
  })
  const data = await res.json()
  return data.url as string
}

export default async function PreviewCard({ pageUrl }: { pageUrl: string }) {
  const imgUrl = await getScreenshot(pageUrl)
  return Page preview
}

OG Image Generation with ImageResponse

For dynamic OG images, combine Next.js ImageResponse with SnapAPI. Create app/og/route.tsx:

import { ImageResponse } from "next/og"

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const targetUrl = searchParams.get("url") ?? ""

  const params = new URLSearchParams({
    access_key: process.env.SNAPAPI_KEY!,
    url: targetUrl,
    response_type: "json",
    width: "1200", height: "630"
  })
  const res = await fetch(`https://snapapi.pics/api/screenshot?${params}`)
  const { url: screenshotUrl } = await res.json()

  return new ImageResponse(
    ,
    { width: 1200, height: 630 }
  )
}

Server Actions for Form-Based Capture

Use Server Actions to trigger screenshot captures from client components without building a separate API endpoint:

"use server"
import { revalidatePath } from "next/cache"

export async function captureAction(formData: FormData) {
  const url = formData.get("url") as string
  const params = new URLSearchParams({
    access_key: process.env.SNAPAPI_KEY!,
    url, response_type: "json"
  })
  const res = await fetch(`https://snapapi.pics/api/screenshot?${params}`)
  const data = await res.json()
  await db.captures.create({ data: { url, screenshotUrl: data.url } })
  revalidatePath("/captures")
}

Caching Strategy in App Router

Next.js App Router fetch caching integrates naturally with SnapAPI calls. Pass next.revalidate in seconds to set ISR-style cache TTL. For screenshot URLs that rarely change, cache for 24 hours: next: revalidate: 86400. For pages that update frequently such as news articles or live dashboards, use a shorter TTL like 300 seconds or opt out of caching entirely with next: revalidate: 0. For on-demand revalidation, call revalidateTag or revalidatePath after your content updates to purge stale screenshots.

Environment Variables

Add SNAPAPI_KEY to your .env.local file. In Next.js App Router, environment variables without the NEXT_PUBLIC_ prefix are only available on the server, which is exactly right for API keys. Never prefix your SnapAPI key with NEXT_PUBLIC_ as this would expose it in the browser bundle. Configure the variable in your Vercel, Netlify, or cloud deployment environment settings dashboard for production deployments.

TypeScript Types

interface SnapAPIResponse {
  url: string
  width: number
  height: number
  format: string
  size: number
  took: number
}

async function screenshot(pageUrl: string): Promise {
  const params = new URLSearchParams({
    access_key: process.env.SNAPAPI_KEY!,
    url: pageUrl,
    response_type: "json"
  })
  const res = await fetch(`https://snapapi.pics/api/screenshot?${params}`)
  if (!res.ok) throw new Error("SnapAPI error: " + res.status)
  return res.json()
}

Get Started Free

SnapAPI works out of the box with Next.js App Router. Add your API key to .env.local, create a Route Handler, and your first screenshot is ready in under ten minutes. Free tier includes 200 captures per month. Paid plans start at $19 per month for 5,000 screenshots.

Get Free API Key

Advanced Next.js App Router Screenshot Patterns

The Next.js App Router introduced powerful new primitives for data fetching, caching, and streaming that change how external API integrations like SnapAPI should be structured. This section covers production-ready patterns that take full advantage of App Router features.

Parallel Data Fetching with Promise.all

When a Server Component needs to generate screenshots of multiple URLs simultaneously, use Promise.all to parallelize the API calls rather than awaiting them sequentially:

async function getMultipleScreenshots(urls: string[]) {
  return Promise.all(
    urls.map(async (url) => {
      const params = new URLSearchParams({
        access_key: process.env.SNAPAPI_KEY!,
        url, response_type: "json", width: "1280"
      })
      const res = await fetch(`https://snapapi.pics/api/screenshot?${params}`,
        { next: { revalidate: 3600 } })
      return res.json()
    })
  )
}

Streaming Screenshots with Suspense

For pages that display many screenshots, use React Suspense to stream each screenshot as it resolves rather than waiting for all of them:

// app/gallery/page.tsx
import { Suspense } from "react"
import ScreenshotCard from "./ScreenshotCard"

export default function GalleryPage({ urls }: { urls: string[] }) {
  return (
    
{urls.map(url => ( }> ))}
) }

On-Demand Revalidation After Content Updates

Use Next.js cache tags to invalidate screenshot caches when the underlying content changes. Tag your SnapAPI fetches with a content-specific key, then call revalidateTag when content is updated via a Route Handler or Server Action:

// Tag the fetch
const res = await fetch(snapApiUrl, {
  next: { tags: ["screenshot-" + encodeURIComponent(url)] }
})

// Invalidate on content update
import { revalidateTag } from "next/cache"

export async function POST(request: Request) {
  const { updatedUrl } = await request.json()
  revalidateTag("screenshot-" + encodeURIComponent(updatedUrl))
  return Response.json({ revalidated: true })
}

Middleware-Based Screenshot Proxy

For applications where many routes need screenshot functionality, create a Next.js Middleware that intercepts screenshot requests and proxies them to SnapAPI, avoiding repeated Route Handler boilerplate across the project:

// middleware.ts
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"

export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith("/api/snap")) {
    const url = request.nextUrl.searchParams.get("url")
    const snapUrl = new URL("https://snapapi.pics/api/screenshot")
    snapUrl.searchParams.set("access_key", process.env.SNAPAPI_KEY!)
    if (url) snapUrl.searchParams.set("url", url)
    snapUrl.searchParams.set("response_type", "json")
    return NextResponse.rewrite(snapUrl)
  }
}

export const config = { matcher: ["/api/snap"] }

Error Boundaries for Failed Screenshots

Wrap screenshot components in error boundaries to handle API failures gracefully without breaking the entire page render. Create a client-side error boundary component and use it alongside Suspense for robust handling of both loading and error states in your screenshot gallery components. Always provide a meaningful fallback UI when a screenshot fails so the rest of the page remains functional and users are not left with a broken layout.

Next.js App Router and SnapAPI: Common Questions

Should I use a Route Handler or a Server Component to call SnapAPI? Use a Route Handler when you need the screenshot URL from client-side code or when you want to expose the capture functionality as an API endpoint for other services. Use a Server Component when you want to embed the screenshot directly in a server-rendered page, taking advantage of Next.js fetch caching. Can I use SnapAPI with Next.js middleware? Yes, you can proxy SnapAPI requests through middleware as shown in the advanced patterns section, but be aware that middleware runs in the Edge Runtime which has different capabilities than the Node.js runtime. The fetch API is available in Edge Runtime, so the basic proxying pattern works. For complex logic, prefer a Route Handler in the Node.js runtime. How do I handle TypeScript types for SnapAPI responses in a Next.js project? Define a SnapAPIResponse interface as shown in the TypeScript section and use it to type your fetch results. For strict TypeScript projects, add runtime validation using zod to validate the API response shape before using it in your components. Does SnapAPI work with Turbopack in Next.js development? Yes, SnapAPI is an external API and is not affected by whether you use Webpack or Turbopack for bundling. The only relevant configuration is your environment variables and fetch cache settings, both of which work identically with either bundler. Get your free API key at snapapi.pics and start capturing screenshots in your Next.js App Router project today with no credit card required.

Start Building Today

SnapAPI is live and ready. Create your free account in under two minutes, copy your API key, and make your first screenshot API call from any language or framework. No credit card required for the free tier. The $19 per month plan unlocks 5,000 captures and is the right fit for most teams moving from prototype to production. The $79 per month plan at 50,000 captures scales with growing engineering teams. Enterprise plans with custom infrastructure, SLAs, and dedicated support are available on request. Contact us through the website and we respond to all enterprise inquiries within one business day.