Fetch the URL in load, print it in svelte:head.
A `load` function in `+page.server.ts` runs on the server only, so the key never reaches the browser. The result flows into page data and renders inside `<svelte:head>`.
src/routes/blog/[slug]/+page.server.ts
import { IMAGE_API_KEY } from '$env/static/private';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, fetch }) => {
const post = await getPost(params.slug);
const res = await fetch('https://imageapi.dev/v1/images?wait=true', {
method: 'POST',
headers: {
Authorization: `Bearer ${IMAGE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
template: 'blog-og',
modifications: { title: post.title, author: post.author, site: 'yourblog.com' },
}),
});
const { url: ogImage } = await res.json();
return { post, ogImage };
};
Importing from `$env/static/private` is checked at build time — try to use it in a universal `+page.ts` and SvelteKit fails the build. That is deliberate, and it is what stops you from leaking the key.
With ?wait=true the finished image arrives on the same connection — usually in about a second.
{
"id": "img_01m08s3ez1zcv28h335g9923bw",
"status": "completed",
"url": "https://cdn.imageapi.dev/renders/img_01m08s3ez1zcv28h335g9923bw.png",
"duration_ms": 912
}
A hundred images a month free, then $9 for two thousand. The same post rendered twice returns the existing URL and uses no quota — so rebuilding your site costs nothing.