From 9d1170fb9a8e53eb0bb86fd659756cedcf5d576c Mon Sep 17 00:00:00 2001 From: Hoid Date: Mon, 2 Mar 2026 12:07:08 +0100 Subject: [PATCH] feat: add /compare and /guides/quick-start SEO pages - Compare page: SnapAPI vs ScreenshotOne, URLBox, ApiFlash, CaptureKit, GetScreenshot - Quick-start guide: 5-step developer tutorial with cURL, GET, SDK examples - Both pages: dark theme, JSON-LD, OG tags, canonical URLs, mobile responsive - Added clean URL redirects in routing - Updated sitemap.xml and index.html nav - Added seo-pages.test.ts (10 tests, all passing) --- public/compare.html | 212 ++++++++++++++++++++++++ public/guides/quick-start.html | 220 +++++++++++++++++++++++++ public/index.html | 2 + public/sitemap.xml | 2 + src/index.ts | 4 + src/routes/__tests__/seo-pages.test.ts | 107 ++++++++++++ 6 files changed, 547 insertions(+) create mode 100644 public/compare.html create mode 100644 public/guides/quick-start.html create mode 100644 src/routes/__tests__/seo-pages.test.ts diff --git a/public/compare.html b/public/compare.html new file mode 100644 index 0000000..3fe5301 --- /dev/null +++ b/public/compare.html @@ -0,0 +1,212 @@ + + + + + +Screenshot API Comparison 2026 โ€” SnapAPI vs Alternatives | SnapAPI + + + + + + + + + + + + + + + + + + +
+
+

Screenshot API Comparison 2026

+ +

Choosing the right screenshot API depends on your requirements โ€” pricing, data residency, features, and developer experience. Here's an honest look at how the major screenshot APIs compare so you can pick the best fit for your project.

+ +

The Contenders

+ +
+
+

๐Ÿ“ธ SnapAPI

+

EU-hosted screenshot API with simple EUR pricing.

+
    +
  • EU data residency (Germany)
  • +
  • POST & GET endpoints
  • +
  • Built-in response caching
  • +
  • Free playground, no signup
  • +
  • Node.js & Python SDKs
  • +
  • Pricing in EUR
  • +
+
+
+

ScreenshotOne

+

Feature-rich API with global CDN.

+
    +
  • Extensive rendering options
  • +
  • US-based infrastructure
  • +
  • USD pricing
  • +
+
+
+

URLBox

+

Established screenshot service with retina support.

+
    +
  • Retina rendering
  • +
  • Webhook notifications
  • +
  • USD pricing
  • +
+
+
+

ApiFlash

+

Chrome-based screenshot API with CDN caching.

+
    +
  • AWS-powered rendering
  • +
  • Built-in CDN
  • +
  • USD pricing
  • +
+
+
+

CaptureKit

+

Modern API with generous free tier.

+
    +
  • Multiple output formats
  • +
  • Custom viewport sizes
  • +
  • USD pricing
  • +
+
+
+

GetScreenshot

+

Simple screenshot API for quick integrations.

+
    +
  • Simple REST API
  • +
  • PNG & JPEG output
  • +
  • USD pricing
  • +
+
+
+ +

Why SnapAPI?

+ +

Every API on this list can take a screenshot. What sets SnapAPI apart is where and how it does it:

+ +
    +
  • ๐Ÿ‡ช๐Ÿ‡บ EU-hosted & GDPR compliant โ€” All rendering happens on servers in Germany. Your data never leaves the EU. No extra DPAs or compliance headaches.
  • +
  • ๐Ÿ’ถ Simple EUR pricing โ€” No currency conversion, no hidden fees. Plans start at โ‚ฌ9/month with clear per-screenshot pricing.
  • +
  • ๐Ÿ”— GET & POST endpoints โ€” Use GET requests to embed screenshots directly in <img> tags. No server-side code needed for simple use cases.
  • +
  • โšก Built-in caching โ€” Response caching out of the box. Repeated requests for the same URL return cached results instantly.
  • +
  • ๐ŸŽฎ Free playground โ€” Try the API in your browser without creating an account or entering payment details.
  • +
  • ๐Ÿ“ฆ Official SDKs โ€” First-class Node.js and Python SDKs to get you started in minutes.
  • +
+ +
+

Try SnapAPI Free

+

No signup required. Test screenshots in the playground, then get an API key when you're ready.

+ Get Your API Key โ†’ +
+
+
+ + + + diff --git a/public/guides/quick-start.html b/public/guides/quick-start.html new file mode 100644 index 0000000..f27e6ce --- /dev/null +++ b/public/guides/quick-start.html @@ -0,0 +1,220 @@ + + + + + +Quick-Start Guide โ€” Take Your First Screenshot with SnapAPI + + + + + + + + + + + + + + + + + + +
+
+

Quick-Start Guide: Your First Screenshot in 5 Minutes

+ +

This guide walks you through everything you need to go from zero to capturing screenshots with SnapAPI. No prior experience required.

+ +
+
1
+

Get an API key

+

Head to the pricing page and pick a plan. You'll receive your API key immediately after signing up. Plans start at โ‚ฌ9/month.

+

Want to try first? Use the free playground โ€” no signup needed.

+
+ +
+
2
+

Take your first screenshot

+

Use curl to call the POST endpoint and capture a screenshot:

+ +
+
cURL
+
curl -X POST https://snapapi.eu/v1/screenshot \
+  -H "Content-Type: application/json" \
+  -H "X-API-Key: YOUR_API_KEY" \
+  -d '{"url": "https://example.com", "format": "png"}' \
+  --output screenshot.png
+
+ +

That's it โ€” you'll have a screenshot.png file on your machine.

+
+ +
+
3
+

Use the GET endpoint for embedding

+

SnapAPI supports GET requests, which means you can embed screenshots directly in <img> tags โ€” no server-side code needed:

+ +
+
HTML
+
<img src="https://snapapi.eu/v1/screenshot?url=https://example.com&format=png&apiKey=YOUR_API_KEY"
+     alt="Screenshot of example.com" />
+
+ +

This is perfect for dashboards, link previews, and documentation where you want live screenshots without any backend logic.

+
+ +
+
4
+

Use caching headers

+

SnapAPI includes built-in response caching. When you request the same URL multiple times, subsequent requests return the cached result instantly โ€” saving you both time and credits.

+

Cache behavior is automatic. The Cache-Control headers in the response tell you the cache status.

+
+ +
+
5
+

Try the Node.js and Python SDKs

+

For deeper integrations, use the official SDKs:

+ +
+
Node.js
+
import { SnapAPI } from 'snapapi';
+
+const client = new SnapAPI('YOUR_API_KEY');
+const screenshot = await client.take({
+  url: 'https://example.com',
+  format: 'png',
+  width: 1280,
+  height: 720
+});
+
+ +
+
Python
+
from snapapi import SnapAPI
+
+client = SnapAPI("YOUR_API_KEY")
+screenshot = client.take(
+    url="https://example.com",
+    format="png",
+    width=1280,
+    height=720
+)
+
+
+ +
+

Ready to Build?

+

Get your API key and start capturing screenshots in production.

+ Get Your API Key โ†’ +
+ + +
+
+ + + + diff --git a/public/index.html b/public/index.html index 4b53948..065cb5f 100644 --- a/public/index.html +++ b/public/index.html @@ -244,6 +244,8 @@ footer{border-top:1px solid var(--border);padding:48px 24px 32px;background:var( API Docs Swagger Usage + Compare + Quick Start Get API Key diff --git a/public/sitemap.xml b/public/sitemap.xml index b03a934..87c9000 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -5,6 +5,8 @@ https://snapapi.eu/use-cases/social-media-previewsmonthly0.7 https://snapapi.eu/use-cases/website-monitoringmonthly0.7 https://snapapi.eu/use-cases/pdf-reportsmonthly0.7 + https://snapapi.eu/comparemonthly0.7 + https://snapapi.eu/guides/quick-startmonthly0.7 https://snapapi.eu/statusalways0.3 https://snapapi.eu/impressum.htmlyearly0.2 https://snapapi.eu/privacy.htmlyearly0.2 diff --git a/src/index.ts b/src/index.ts index 7986fb9..b032bc8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -135,6 +135,10 @@ for (const page of ["social-media-previews", "website-monitoring", "pdf-reports" app.get(`/use-cases/${page}`, (_req, res) => res.redirect(301, `/use-cases/${page}.html`)); } +// Clean URLs for SEO pages +app.get("/compare", (_req, res) => res.redirect(301, "/compare.html")); +app.get("/guides/quick-start", (_req, res) => res.redirect(301, "/guides/quick-start.html")); + // Static files (landing page) app.use(express.static(path.join(__dirname, "../public"), { etag: true })); diff --git a/src/routes/__tests__/seo-pages.test.ts b/src/routes/__tests__/seo-pages.test.ts new file mode 100644 index 0000000..c181917 --- /dev/null +++ b/src/routes/__tests__/seo-pages.test.ts @@ -0,0 +1,107 @@ +import { describe, it, expect } from 'vitest' +import request from 'supertest' +import express from 'express' +import path from 'path' +import { fileURLToPath } from 'url' +import fs from 'fs' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const publicDir = path.join(__dirname, '../../../public') + +function createApp() { + const app = express() + + // Clean URL redirects matching index.ts + app.get('/compare', (_req, res) => res.redirect(301, '/compare.html')) + app.get('/guides/quick-start', (_req, res) => res.redirect(301, '/guides/quick-start.html')) + + app.use(express.static(publicDir, { etag: true })) + return app +} + +describe('Compare Page', () => { + const app = createApp() + + it('GET /compare.html returns 200', async () => { + const res = await request(app).get('/compare.html') + expect(res.status).toBe(200) + expect(res.headers['content-type']).toContain('text/html') + }) + + it('GET /compare redirects 301 to .html', async () => { + const res = await request(app).get('/compare') + expect(res.status).toBe(301) + expect(res.headers.location).toBe('/compare.html') + }) + + it('contains required SEO elements', async () => { + const res = await request(app).get('/compare.html') + const html = res.text + expect(html).toMatch(/.+<\/title>/) + expect(html).toMatch(/<meta name="description" content=".+"/) + expect(html).toMatch(/<meta property="og:title"/) + expect(html).toMatch(/<meta property="og:description"/) + expect(html).toMatch(/<meta property="og:type" content="website"/) + expect(html).toMatch(/<link rel="canonical"/) + expect(html).toMatch(/<meta name="twitter:card"/) + expect(html).toContain('"@type":"WebPage"') + }) + + it('mentions competitors factually', async () => { + const res = await request(app).get('/compare.html') + const html = res.text + for (const competitor of ['ScreenshotOne', 'URLBox', 'ApiFlash', 'CaptureKit', 'GetScreenshot']) { + expect(html).toContain(competitor) + } + }) +}) + +describe('Quick-Start Guide', () => { + const app = createApp() + + it('GET /guides/quick-start.html returns 200', async () => { + const res = await request(app).get('/guides/quick-start.html') + expect(res.status).toBe(200) + expect(res.headers['content-type']).toContain('text/html') + }) + + it('GET /guides/quick-start redirects 301 to .html', async () => { + const res = await request(app).get('/guides/quick-start') + expect(res.status).toBe(301) + expect(res.headers.location).toBe('/guides/quick-start.html') + }) + + it('contains required SEO elements', async () => { + const res = await request(app).get('/guides/quick-start.html') + const html = res.text + expect(html).toMatch(/<title>.+<\/title>/) + expect(html).toMatch(/<meta name="description" content=".+"/) + expect(html).toMatch(/<meta property="og:title"/) + expect(html).toMatch(/<meta property="og:description"/) + expect(html).toMatch(/<link rel="canonical"/) + expect(html).toMatch(/<meta name="twitter:card"/) + expect(html).toContain('"@type":"HowTo"') + }) + + it('contains step-by-step content', async () => { + const res = await request(app).get('/guides/quick-start.html') + const html = res.text + expect(html).toContain('curl') + expect(html).toContain('API key') + expect(html).toContain('GET') + }) +}) + +describe('Sitemap & Index updates', () => { + it('sitemap contains new URLs', () => { + const sitemap = fs.readFileSync(path.join(publicDir, 'sitemap.xml'), 'utf-8') + expect(sitemap).toContain('https://snapapi.eu/compare') + expect(sitemap).toContain('https://snapapi.eu/guides/quick-start') + }) + + it('index.html has links to new pages', () => { + const index = fs.readFileSync(path.join(publicDir, 'index.html'), 'utf-8') + expect(index).toContain('/compare') + expect(index).toContain('/guides/quick-start') + }) +})