April 2026 · 8 min read · Blog

Screenshot API for Vue.js — Composables, Nuxt Integration, and Scheduled Captures

A practical guide to integrating SnapAPI into Vue 3 apps and Nuxt projects, with reusable composables, server-side rendering patterns, and automated screenshot pipelines.

Why Vue Developers Need a Screenshot API

Vue 3 and Nuxt have become go-to choices for content-heavy applications: news portals, SaaS dashboards, documentation sites, and e-commerce storefronts. All of these generate a common need — reliable, programmatic screenshots for social sharing previews, PDF exports, visual regression testing, and content thumbnails. Rolling your own Playwright server adds operational complexity that has nothing to do with your core product. SnapAPI is a REST endpoint that handles all of it.

Installation and Quick Start

SnapAPI has no Vue-specific SDK — it's a plain REST API, so you call it with fetch or axios. Install SnapAPI's JavaScript SDK if you want typed helpers:

npm install @snapapi/js

Or call the API directly with native fetch — no dependencies required. Get your free API key at snapapi.pics (200 screenshots/month free, no credit card).

Building a useScreenshot Composable

Composables are Vue 3's answer to reusable stateful logic. A useScreenshot composable wraps the SnapAPI call with loading and error state, making it trivially easy to add screenshot capability to any component:

// composables/useScreenshot.ts
import { ref } from 'vue'

export function useScreenshot() {
  const loading = ref(false)
  const error = ref<string | null>(null)
  const imageUrl = ref<string | null>(null)

  async function capture(url: string, options: Record<string, string> = {}) {
    loading.value = true
    error.value = null
    imageUrl.value = null

    try {
      const params = new URLSearchParams({
        url,
        format: 'webp',
        width: '1280',
        height: '800',
        access_key: import.meta.env.VITE_SNAP_KEY,
        ...options
      })

      const resp = await fetch(`https://api.snapapi.pics/v1/screenshot?${params}`)
      if (!resp.ok) throw new Error(`SnapAPI error: ${resp.status}`)

      const blob = await resp.blob()
      imageUrl.value = URL.createObjectURL(blob)
    } catch (e) {
      error.value = e instanceof Error ? e.message : 'Unknown error'
    } finally {
      loading.value = false
    }
  }

  return { loading, error, imageUrl, capture }
}

Use it in any component:

<script setup lang="ts">
import { useScreenshot } from '@/composables/useScreenshot'

const { loading, error, imageUrl, capture } = useScreenshot()
</script>

<template>
  <div>
    <button @click="capture('https://example.com')" :disabled="loading">
      {{ loading ? 'Capturing...' : 'Take Screenshot' }}
    </button>
    <img v-if="imageUrl" :src="imageUrl" alt="Screenshot" />
    <p v-if="error" class="error">{{ error }}</p>
  </div>
</template>

Nuxt Server Routes for Safe API Key Handling

In Nuxt, never expose your SnapAPI key to the browser. Use a Nuxt server route (or API route) to proxy the request server-side:

// server/api/screenshot.get.ts
export default defineEventHandler(async (event) => {
  const { url, format = 'webp', width = '1280' } = getQuery(event)

  if (!url || typeof url !== 'string') {
    throw createError({ statusCode: 400, statusMessage: 'url is required' })
  }

  const params = new URLSearchParams({
    url,
    format: format as string,
    width: width as string,
    access_key: process.env.SNAP_KEY!
  })

  const resp = await fetch(`https://api.snapapi.pics/v1/screenshot?${params}`)
  if (!resp.ok) throw createError({ statusCode: resp.status, statusMessage: 'SnapAPI error' })

  const contentType = resp.headers.get('content-type') ?? 'image/webp'
  setResponseHeader(event, 'content-type', contentType)
  setResponseHeader(event, 'cache-control', 'public, max-age=3600')

  return sendStream(event, resp.body!)
})

Now your Nuxt frontend calls /api/screenshot?url=... — the API key never leaves your server. The cache-control header means Nuxt's CDN layer (Vercel, Netlify, Cloudflare) will cache repeated screenshot requests automatically.

OG Image Generation for Nuxt Content

Nuxt Content sites need Open Graph images for every post. Hook into the content:file:afterParse hook (Nuxt 3 Content v2) or a build script to generate screenshots of all published posts:

// scripts/generate-og.ts
import { readdir } from 'fs/promises'

const BASE = 'https://your-nuxt-site.com'
const KEY = process.env.SNAP_KEY!

const slugs = (await readdir('content/blog'))
  .filter(f => f.endsWith('.md'))
  .map(f => f.replace('.md', ''))

for (const slug of slugs) {
  const url = `${BASE}/blog/${slug}`
  const resp = await fetch(
    `https://api.snapapi.pics/v1/screenshot?url=${encodeURIComponent(url)}&format=webp&width=1200&height=630&access_key=${KEY}`
  )
  const buffer = Buffer.from(await resp.arrayBuffer())
  await writeFile(`public/og/${slug}.webp`, buffer)
  console.log('Generated OG:', slug)
}

Run this script as a post-build step in your CI pipeline. The generated WebP images are served as static assets from your CDN — zero runtime cost, perfect cache hit rate.

Pinia Store for Screenshot Management

For applications that generate many screenshots (dashboards, report builders, content management tools), a Pinia store keeps captures organised and prevents redundant API calls:

// stores/screenshots.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useScreenshotStore = defineStore('screenshots', () => {
  const cache = ref<Map<string, string>>(new Map())
  const pending = ref<Set<string>>(new Set())

  const isCached = computed(() => (url: string) => cache.value.has(url))

  async function getScreenshot(url: string): Promise<string> {
    if (cache.value.has(url)) return cache.value.get(url)!
    if (pending.value.has(url)) {
      // Wait for in-flight request
      await new Promise(r => setTimeout(r, 200))
      return getScreenshot(url)
    }

    pending.value.add(url)
    const resp = await fetch('/api/screenshot?url=' + encodeURIComponent(url))
    const blob = await resp.blob()
    const objectUrl = URL.createObjectURL(blob)
    cache.value.set(url, objectUrl)
    pending.value.delete(url)
    return objectUrl
  }

  return { cache, isCached, getScreenshot }
})

Visual Regression Testing with Playwright + SnapAPI

Vue component tests with Playwright cover interaction logic, but they won't catch CSS regressions in your production build. SnapAPI visual regression tests complement your Playwright suite by screenshotting the fully deployed app — catching CDN caching issues, third-party script failures, and CSS variables that break differently in production vs. the dev server.

Add a regression.test.ts file that runs post-deploy, screenshots key routes, and diffs against approved baselines stored in your repository. Any pixel diff above 2% triggers a Slack alert and a GitHub Actions annotation.

Getting Started

SnapAPI is free for 200 screenshots per month with no credit card required. Sign up at snapapi.pics, get your key, drop it in .env as VITE_SNAP_KEY (client-exposed) or SNAP_KEY (server-only), and your Vue or Nuxt app can start capturing screenshots immediately. SDKs for JavaScript, Python, Go, PHP, Swift, and Kotlin are available on GitHub for teams that prefer typed wrappers over raw fetch calls.

Add screenshot capabilities to your Vue app today

Free tier: 200 screenshots/month. No credit card required.

Get Free API Key

Advanced Vue and Nuxt Screenshot Patterns

Server-Side Rendering and Hydration Considerations

Nuxt applications with SSR introduce a subtle screenshot timing challenge: the server sends a fully rendered HTML page, but Vue hydrates it on the client, potentially causing layout shifts or temporarily hiding elements that are only visible post-hydration. SnapAPI handles this with the wait_for selector — set a data-hydrated attribute on your root component in onMounted, and wait for it before capturing:

// app.vue
onMounted(() => {
  document.documentElement.setAttribute('data-hydrated', 'true')
})

// Server-side SnapAPI call
const params = { url, wait_for: '[data-hydrated="true"]', format: 'webp', access_key: KEY }

This guarantees Vue has finished hydrating before the screenshot is taken, eliminating FOUC or partially hydrated layouts from your captures.

Using Nitro Tasks for Scheduled Screenshot Jobs

Nuxt 3.9 ships Nitro Tasks, a first-class API for scheduled server-side jobs. Use a Nitro task to refresh OG images for your most important pages every 24 hours. Schedule via nitro.scheduledTasks in nuxt.config.ts. The OG images are stored in Nitro asset storage, served as static files from your CDN on subsequent requests at zero compute cost.

Generating PDF Reports from Vue Dashboards

SaaS dashboards built with Vue frequently need PDF export. Rather than reimplementing charts and tables in a PDF library (which inevitably looks different from the screen), screenshot the dashboard with SnapAPI and embed the image in a PDF. Use SnapAPI full-page mode with a wide viewport to capture the complete layout, then pass the resulting image to PDFKit, jsPDF, or a similar library.

For dashboards with multiple sections, use the clip parameter to capture specific regions. This avoids visual compression when a tall full-page screenshot is scaled to fit a PDF page.

Pinia Store for Screenshot Management

For applications that generate many screenshots — dashboards, report builders, CMS tools — a Pinia store keeps captures organised and prevents redundant API calls. Store a URL-keyed map of object URLs created from screenshot blobs, check the cache before making an API call, and handle concurrent requests for the same URL by queuing them behind the first in-flight request rather than making duplicate API calls. This pattern is especially valuable when users interact with a list of previews and might rapidly hover or click through many URLs simultaneously.

Visual Regression Testing Strategy

Vue component tests with Playwright cover interaction logic but miss CSS regressions in your production build. SnapAPI visual regression tests complement your Playwright suite by screenshotting the fully deployed app — catching CDN caching issues, third-party script failures, and CSS variables that break differently in production versus the dev server. Store approved baselines in Git LFS. On each deploy, capture fresh screenshots with SnapAPI, run pixelmatch against baselines, and report the diff percentage as a GitHub Actions check. Reviewers see the visual diff directly in the PR. Free tier includes 200 captures per month — sufficient for a regression suite covering all key routes in a typical Nuxt application.