DEV Community

Boehner
Boehner

Posted on

SnapAPI in Production: The Patterns I Wish I'd Known Earlier

The SnapAPI docs are good. They tell you what each endpoint does. What they don't cover is how to use those endpoints in systems that actually run in production — with error handling, caching, concurrency limits, CI integration, and real framework patterns.

I've been building with SnapAPI for a while and I wrote down the patterns that made the difference. Here are the most useful ones.

1. Always use environment variables. Never hardcode.

// Bad — never do this
const client = new SnapAPI('snap_abc123');

// Good
const client = new SnapAPI(); // reads SNAPAPI_KEY from env
Enter fullscreen mode Exit fullscreen mode

Set it in your platform's secrets manager, not in code. For local dev, .env file (never committed).

2. Error handling: loud in CI, graceful in user-facing code

// CI pipeline — fail loudly so you notice broken deploys
async function validatePage(url) {
  return client.analyze(url); // throws on error → CI step fails
}

// User-facing feature — fail gracefully
async function tryGetPreview(url) {
  try {
    return await client.metadata(url);
  } catch (err) {
    logger.warn('metadata failed', { url, status: err.status });
    return null; // caller handles null gracefully
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Caching: what to handle yourself vs. rely on SnapAPI

SnapAPI has a built-in 30-minute cache. Use it deliberately:

Use case Strategy
OG preview generation Rely on SnapAPI's cache — same URL, same response
Competitor monitoring Store state in your DB, call SnapAPI on schedule
Screenshot gallery Save to S3/R2, refresh on schedule
CI page validation No caching — every run should be fresh

For longer-lived caches at your layer, a simple Map with TTL works for small scale. Replace with Redis in production.

4. Concurrency limiting

Free tier allows 1 concurrent request. Starter allows 2. If you're looping over URLs without a limiter, you'll get 429s.

function createLimiter(concurrency) {
  let active = 0;
  const queue = [];
  return async fn => {
    if (active >= concurrency) await new Promise(r => queue.push(r));
    active++;
    try { return await fn(); }
    finally { active--; if (queue.length) queue.shift()(); }
  };
}

const limit = createLimiter(2); // Starter tier
const results = await Promise.all(
  urls.map(url => limit(() => client.metadata(url)))
);
Enter fullscreen mode Exit fullscreen mode

For high-volume work, use /v1/batch instead of looping.

5. CI/CD page validation

One of the most useful patterns: after every deploy, assert that key pages still have their CTAs and OG tags. Catch regressions before users find them.

// post-deploy-check.js
const PAGES = [
  { url: 'https://yoursite.com', cta: 'Get started' },
  { url: 'https://yoursite.com/pricing', cta: 'Start free' },
];

async function main() {
  let failed = false;
  for (const page of PAGES) {
    const data = await client.analyze(page.url);
    const ctas = Array.isArray(data.cta) ? data.cta : [data.primary_cta];
    if (!ctas.some(c => c.includes(page.cta))) {
      console.error(`FAIL ${page.url}: expected "${page.cta}", got: ${ctas.join(', ')}`);
      failed = true;
    } else {
      console.log(`OK   ${page.url} ✓`);
    }
  }
  if (failed) process.exit(1);
}
main();
Enter fullscreen mode Exit fullscreen mode

Wire it into GitHub Actions as a post-deploy check.

6. Next.js link preview — keep the key server-side

When building a link unfurl feature, never call SnapAPI from the browser. Use an API route:

// pages/api/preview.js
export default async function handler(req, res) {
  const { url } = req.query;
  // Validate URL first (prevent SSRF)
  const meta = await client.metadata(url).catch(() => null);
  if (!meta) return res.status(502).json({ error: 'preview unavailable' });

  res.setHeader('Cache-Control', 'public, max-age=3600');
  res.json({ title: meta.og_title || meta.title, image: meta.og_image, description: meta.og_description });
}
Enter fullscreen mode Exit fullscreen mode

Your API key never leaves your server. The browser calls /api/preview?url=... and gets clean JSON back.

7. The three OG card anti-patterns

When generating OG images with /v1/render:

  • Don't use web fonts without loading them@import url(...) won't work in a server-rendered context. Stick to system fonts or inline base64-encode your font.
  • Don't use relative paths — all image src attributes must be absolute URLs.
  • Don't forget the dimensions in the HTML — set width and height on the root element to match what you're requesting (e.g., width:1200px;height:630px), not just in the API call.

8. Troubleshooting in 30 seconds

Symptom Most likely cause
429 on sequential calls Missing concurrency limiter
Blank/white screenshots Page requires auth or has full-screen loading screen
og_image always empty Tag is JS-injected but page is blocking headless
Slow response on repeat calls Cache not being hit — URL params differ (encoding, trailing slash)
Screenshot cuts off full_page=false and your content is below the viewport

I put all of this — plus N8N integration patterns, Python pipeline examples, visual regression testing, OG card generation at scale, and the full troubleshooting reference — into a guide called the SnapAPI Integration Playbook. It's $19 on Gumroad: Web Intelligence Playbook

Free API key (100 calls/month, no credit card): snapapi.tech/start

Top comments (0)