Site-Shot

Tutorial ·Sep 2, 2026 ·7 min read

How to Add Website Screenshots to a Lovable App

Ask Lovable to add screenshots to your app and it will try. Whether it produces something that works depends almost entirely on how much you told it up front — because Lovable makes an architectural decision on your behalf, and you want it to make the right one.

Its documentation describes the behaviour plainly: "When you ask Lovable to integrate an API, it automatically chooses the correct approach," and "If the API requires authentication, Lovable prompts you to enable the built-in backend (Cloud) and add a secret, and automatically creates an Edge Function to protect sensitive credentials."

That last clause is the whole game. You want the Edge Function. A vague prompt can get you a browser-side fetch instead, which fails — and fails in a way that has produced a lot of confidently wrong advice.

The prompt

Lovable's own guidance is to give it the endpoint, the auth method, the headers, and request and response examples. So:

Add a screenshot feature. When the user submits a URL, capture a full-page screenshot of it and display the image in the app.

Use the Site-Shot API. It is a GET request to https://api.site-shot.com/ with query parameters: url (the page to capture), userkey (the API key), full_size=1, no_ads=1, no_cookie_popup=1. It responds with the raw PNG image bytes and Content-Type: image/png.

The API key is secret and must never reach the browser. Put the call in an Edge Function and store the key as a secret named SITESHOT_API_KEY.

In the Edge Function, check the response status before reading the body — a failed capture comes back non-2xx and its body can still be a valid PNG error card, so the status is the only reliable signal. On success, read the response with arrayBuffer(), upload it to Cloud Storage with contentType: 'image/png', create a signed URL, and return JSON of the form { "url": "..." }. The frontend should render that URL in an <img> tag.

Handle CORS preflight requests in the Edge Function.

That is longer than most people's prompt, and every clause in it is preventing a specific failure below.

Why calling the API from the browser does not work

CORS is what people reach for first here, and with Site-Shot it is the wrong explanation — which is worth spelling out, because a confidently wrong version of it circulates.

The story goes: the app showed a placeholder instead of a real screenshot; the author published the page to a public link; screenshots started working. Conclusion drawn — publishing fixes CORS.

It does not, and with this API there was no CORS error to fix. Measured, a successful capture returns Access-Control-Allow-Origin: *, including when the request carries a preview--myapp.lovable.app origin — so there is no allow-list to be moved into or out of, and a plain GET carrying only query parameters is a simple request that is never preflighted. Whatever the placeholder was, publishing did not fix it by changing CORS.

The real reason to call the API from an Edge Function is the one Lovable's docs give: it is the only way to keep the key out of the browser. A browser-side fetch ships your API key to every visitor, and that is reason enough on its own.

You do still need CORS handling on your own function, because the browser calls that: "To invoke edge functions from the browser, you need to handle CORS Preflight requests."

Return JSON, not bytes

Here is the part where I am going to be explicit about what I do not know, because it matters.

Lovable's documentation says nothing at all about binary responses. Searching every Lovable docs page for base64, blob, arrayBuffer or binary returns one incidental mention of the words "Content-Type, custom headers" and nothing else.

One layer down, the function is a Supabase Edge Function and the frontend reaches it through supabase.functions.invoke, whose reference states the parsing rule:

"Responses are automatically parsed as json, blob and form-data depending on the Content-Type header sent by your function. Responses are parsed as text by default."

So returning PNG bytes without a matching Content-Type gets you a mangled string and a broken <img> with no error. Whether returning them with Content-Type: image/png reliably routes to the blob branch is not documented, and I have not tested it. I am not going to assert it either way.

Which is why the prompt above takes the unambiguous route: the function reads arrayBuffer(), uploads to Storage, and returns plain JSON containing a URL. JSON parsing is the one path the documentation describes without ambiguity, and the frontend gets an ordinary image URL.

Ask the API for the size you want

You cannot fix an oversized screenshot after the fact. Supabase is explicit about what Edge Functions cannot load: "Node Libraries that require multithreading are not supported. Examples: libvips, sharp."

No resizing, no thumbnailing, no format conversion server-side. Whatever dimensions you need, request them at capture time.

Two ways your saved screenshots go dead

Signed URLs expire after an hour. Private bucket links are "a temporary signed URL that expires after one hour." An app that stores that URL in a database and renders it tomorrow shows broken images. Store the path and mint a fresh signed URL on each read.

The obvious workaround is off by default. "Public storage buckets are blocked by default on all plans." A workspace owner or admin has to disable that block in Privacy & security before you can use a public bucket — which is a deliberate decision to make, not a checkbox to click past, since it makes every object in that bucket world-readable.

Auto-pause will stop a scheduled screenshot app

If you build anything that captures on a schedule, know this one before you rely on it.

"Edge functions are unavailable while a project is paused, so features that depend on them stop working until you resume the project." And traffic does not wake it: a paused Cloud project "only resumes when you explicitly click Wake up on the paused project card."

So a nightly capture app stops silently, and stays stopped until a human notices and clicks a button. Lovable is a good place to build a screenshot tool people press a button in. It is not where unattended scheduled capture belongs.

Honest limits

The key is safe only if it stays in the Edge Function. The entire security benefit rests on that. If a later prompt causes Lovable to move the call into the frontend "to make it faster," the key ships to every visitor. Check where the fetch actually lives after any significant regeneration.

This is a prompt, and prompts drift. Unlike a code snippet you paste, what Lovable generates from the prompt above will vary. Read the Edge Function it writes before you trust it — particularly where the key is read from, and whether the response is JSON.

Nothing here is monitoring. A button that captures a page on demand is a feature. Being told when a page changes is a different product.

FAQ

Why does my Lovable app show a broken image instead of a screenshot?

Most often because the Edge Function returned raw image bytes without a matching Content-Type header. The invoke client parses responses as text by default, so the frontend receives a mangled string rather than an image, with no error raised. Returning JSON containing a storage URL avoids the ambiguity entirely.

Does publishing a Lovable app fix CORS errors when calling an API?

No — and with Site-Shot there is nothing for it to fix. Measured, every successful capture returns Access-Control-Allow-Origin set to a wildcard, so a browser call is not blocked from a preview URL or a published one. If screenshots started working after you published, CORS was not the cause. Call the API from an Edge Function anyway, because that is what keeps your API key out of the browser.

Can I resize or thumbnail a screenshot inside a Lovable Edge Function?

No. Node libraries requiring multithreading, including libvips and sharp, are not supported in Edge Functions. Request the dimensions you want from the screenshot API at capture time instead.

Why do my saved screenshots stop loading after a while?

Private storage bucket links are temporary signed URLs that expire after one hour. Store the object path rather than the signed URL and generate a fresh one when rendering. Public buckets are an alternative but are blocked by default on all plans and must be enabled by a workspace owner or admin.

Will a scheduled screenshot feature keep running in Lovable?

Not reliably. Edge functions are unavailable while a project is paused, and a paused project does not resume on incoming traffic — someone has to click Wake up on the paused project card. Scheduled unattended capture belongs somewhere that stays running.

Site-Shot answers a plain GET with a PNG, which keeps the Edge Function short enough to read in one screen and verify that your key never leaves it. See the plans.

← All articles