Skip to content

Render directory screenshots with rasterly instead of Microlink - #562

Merged
ralyodio merged 1 commit into
masterfrom
worktree-rasterly-screenshots
Sep 22, 2026
Merged

ralyodio merged 1 commit into
masterfrom
worktree-rasterly-screenshots

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Swaps the directory's homepage screenshot provider from Microlink to rasterly.dev, which is cheaper.

What changed

src/app/api/directory/fetch-meta/route.ts — captureScreenshot() now asks rasterly first and falls back to Microlink:

  • renderWithRasterly() — GET /v1/screenshot?url=…&format=png&width=1280&height=800 with the key in an X-Api-Key header. The response body is the PNG, so the old two-hop dance (JSON → CDN URL → second fetch) is gone on the happy path.
  • renderWithMicrolink() — the previous logic, unchanged, kept as a keyless fallback.
  • captureScreenshot() now owns the Supabase upload; the providers only return bytes. The storage write, the md5(url)/timestamp.png path and the memoised service client are all untouched.

The fallback is deliberate: RASTERLY_API_KEY is optional and the free tier is 100 renders/month, so a missing key or a failed render still produces a screenshot rather than an empty field.

Config

RASTERLY_API_KEY is already set on the ugig.net Railway service (production, set with --skip-deploys) and stored in the ugig-net--prod team vault. Added to .env.example.

Verification

  • pnpm type-check clean, eslint on the changed route exits 0
  • pnpm test:run — 2134 tests / 222 files pass
  • Live renders through the new code path: https://ugig.net → 106,058-byte PNG, https://profullstack.com → 78,265-byte PNG, both 1280x800, quota header decrementing as expected
  • Key-absent path returns null, so the Microlink fallback runs

🤖 Generated with Claude Code

Microlink was the only screenshot provider for the directory's fetch-meta
route. rasterly.dev is cheaper, so it becomes the primary renderer: one
keyed GET returns the PNG bytes directly, with no second fetch to pull the
image off a CDN.

Microlink stays as a keyless fallback. RASTERLY_API_KEY is optional and the
free tier is 100 renders/month, so when the key is absent or a render fails
the old path still captures the screenshot rather than the field going empty.

The Supabase upload is unchanged; the two providers now only return bytes,
and captureScreenshot owns the storage write.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

45 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 8 | LOW: 36

Severity Rule Location
HIGH js-ssrf-outbound-request scripts/scan-all-skills.ts:38
MEDIUM js-open-redirect src/app/agent-login/AgentLoginForm.tsx:38
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.tsx:79
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:90
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:113
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:144
MEDIUM js-open-redirect src/components/funding/FundingClient.tsx:137
MEDIUM js-dynamic-code-execution src/lib/skills/metadata-extract.ts:300
MEDIUM js-dynamic-code-execution src/lib/skills/security-scan.ts:48
LOW secret-generic-credential cli/src/commands/auth.test.ts:66
LOW secret-generic-credential cli/src/commands/auth.test.ts:85
LOW secret-generic-api-key docs/agents/integration-guide.md:893
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:53
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:68
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:87
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:158
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:182
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:193
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:232
LOW js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:212
LOW js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:223
LOW js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:239
LOW secret-generic-credential src/lib/api.test.ts:126
LOW secret-generic-credential src/lib/api.test.ts:131
LOW js-dynamic-code-execution src/lib/skills/composite-scanner.test.ts:106
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:36
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:44
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:66
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:81
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:94
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:103
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:118
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:144
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:162
LOW js-dynamic-code-execution src/lib/skills/security-scan.test.ts:28
LOW js-dynamic-code-execution src/lib/skills/url-import.test.ts:178
LOW js-dynamic-code-execution src/lib/skills/url-import.test.ts:191
LOW secret-generic-credential src/lib/validations.test.ts:148
LOW secret-generic-credential src/lib/validations.test.ts:512
LOW secret-generic-credential src/lib/validations.test.ts:523
LOW secret-generic-credential src/lib/validations.test.ts:538
LOW secret-generic-credential src/lib/validations.test.ts:548
LOW secret-generic-credential src/lib/validations.test.ts:557
LOW secret-generic-credential src/lib/validations.test.ts:567
LOW secret-generic-credential src/lib/validations.test.ts:582

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit fc3cc72 into master Sep 22, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant